// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <nebula/types/meta/decimal_type.h>
#include <turbo/log/logging.h>

namespace nebula {

    constexpr Type::type Decimal128Type::type_id;

    constexpr Type::type Decimal256Type::type_id;

    std::string DecimalType::compute_fingerprint() const {
        std::stringstream ss;
        ss << type_id_fingerprint(*this) << "[" << byte_width_ << "," << precision_ << ","
           << scale_ << "]";
        return ss.str();
    }


    turbo::Result<std::shared_ptr<DataType>> DecimalType::create(Type::type type_id, int32_t precision,
                                                                 int32_t scale) {
        if (type_id == Type::DECIMAL128) {
            return Decimal128Type::create(precision, scale);
        } else if (type_id == Type::DECIMAL256) {
            return Decimal256Type::create(precision, scale);
        } else {
            return turbo::invalid_argument_error("Not a decimal type_id: ", type_id);
        }
    }

    // Taken from the Apache Impala codebase. The comments next
    // to the return values are the maximum value that can be represented in 2's
    // complement with the returned number of bytes.
    int32_t DecimalType::DecimalSize(int32_t precision) {
       DKCHECK_GE(precision, 1) << "decimal precision must be greater than or equal to 1, got "
                                       << precision;

        // Generated in python with:
        // >>> decimal_size = lambda prec: int(math.ceil((prec * math.log2(10) + 1) / 8))
        // >>> [-1] + [decimal_size(i) for i in range(1, 77)]
        constexpr int32_t kBytes[] = {
                -1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9,
                9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 16, 17,
                17, 18, 18, 18, 19, 19, 20, 20, 21, 21, 21, 22, 22, 23, 23, 23, 24, 24, 25, 25,
                26, 26, 26, 27, 27, 28, 28, 28, 29, 29, 30, 30, 31, 31, 31, 32, 32};

        if (precision <= 76) {
            return kBytes[precision];
        }
        return static_cast<int32_t>(std::ceil((precision / 8.0) * std::log2(10) + 1));
    }

    // ----------------------------------------------------------------------
    // Decimal128 type

    Decimal128Type::Decimal128Type(int32_t precision, int32_t scale)
            : DecimalType(type_id, 16, precision, scale) {
        KCHECK_GE(precision, kMinPrecision);
        KCHECK_LE(precision, kMaxPrecision);
    }

    turbo::Result<std::shared_ptr<DataType>> Decimal128Type::create(int32_t precision, int32_t scale) {
        if (precision < kMinPrecision || precision > kMaxPrecision) {
            return turbo::invalid_argument_error("Decimal precision out of range [", int32_t(kMinPrecision),
                                                 ", ", int32_t(kMaxPrecision), "]: ", precision);
        }
        return std::make_shared<Decimal128Type>(precision, scale);
    }

    // ----------------------------------------------------------------------
    // Decimal256 type

    Decimal256Type::Decimal256Type(int32_t precision, int32_t scale)
            : DecimalType(type_id, 32, precision, scale) {
        KCHECK_GE(precision, kMinPrecision);
        KCHECK_LE(precision, kMaxPrecision);
    }

    turbo::Result<std::shared_ptr<DataType>> Decimal256Type::create(int32_t precision, int32_t scale) {
        if (precision < kMinPrecision || precision > kMaxPrecision) {
            return turbo::invalid_argument_error("Decimal precision out of range [", int32_t(kMinPrecision),
                                                 ", ", int32_t(kMaxPrecision), "]: ", precision);
        }
        return std::make_shared<Decimal256Type>(precision, scale);
    }

    std::string Decimal128Type::to_string(bool show_metadata) const {
        std::stringstream s;
        s << "decimal128(" << precision_ << ", " << scale_ << ")";
        return s.str();
    }

    std::string Decimal256Type::to_string(bool show_metadata) const {
        std::stringstream s;
        s << "decimal256(" << precision_ << ", " << scale_ << ")";
        return s.str();
    }

}  // namespace nebula